﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HtmlAgilityPack;

namespace TestConsole
{
    public class FormHtmlPaser
    {
        private List<FormControlBase> controls = new List<FormControlBase>();

        /// <summary>
        /// 从form字符串中获取控件信息
        /// </summary>
        /// <param name="formHtml"></param>
        /// <returns></returns>
        public List<FormControlBase> GetControls(string formHtml)
        {
            var doc = new HtmlDocument();
            doc.LoadHtml(formHtml);
            controls.Clear();
            ParseNode(doc);
            return controls;
        }

        void ParseNode(HtmlDocument doc)
        {
            foreach (var item in doc.DocumentNode.DescendantsAndSelf())
            {
                PrintNode(item);
            }

            // 后处理
            PostProcess();
        }

        // 在所有组件单独生成完毕后，调用此方法，将checkbox和radio控件进行分组整合
        void PostProcess()
        {
            InputType[] iTypes = { InputType.radio, InputType.checkbox };
            InputType curType = InputType.none;
            string curName = string.Empty;
            List<FormControlBase> newControls = new List<FormControlBase>();
            for (int i = 0; i < controls.Count; i++)
            {
                var input = controls[i] as InputInfo;

                // 不是input直接继续
                if (input == null)
                {
                    newControls.Add(controls[i]);
                    continue;
                }

                // 不是radio和checkbox直接继续
                if (!iTypes.Contains(input.InputType))
                {
                    newControls.Add(controls[i]);
                    continue;
                }

                curType = input.InputType;
                curName = input.Name;
                var startIndex = i;
                // 只有name和type都相同才能继续
                // 退出循环时，i指向第一个不满足要求索引
                while (curType == input.InputType && curName == input.Name && i < controls.Count)
                {
                    i++;
                    input = controls[i] as InputInfo;
                    // 后面不是input直接退出
                    if (input == null)
                        break;
                }
                curType = InputType.none;
                curName = string.Empty;

                var startControl = controls[startIndex] as InputInfo;
                var children = new List<FormControlBase>();
                // 从startIndex到i之间，都是一组
                for (int j = startIndex; j < i; j++)
                {
                    children.Add(controls[j]);
                }

                // 添加组控件
                newControls.Add(new InputInfo
                {
                    GType = (GType)startControl.InputType,
                    Label = startControl.Label,
                    Children = children
                });

                // 本轮循环只处理了 startIndex到i 之间的控件
                // 但是没有处理i所对应的控件，因此在进入下一轮循环前，i--，让下一轮循环继续处理i对应的控件
                i--;
            }
            controls = newControls;
        }

        void PrintNode(HtmlNode item)
        {
            
            //Name就是 div span input
            if (intersTag.Contains(item.Name))
            {
                // NodeType是Element不需要使用
                if (item.Name == "select")
                {
                    controls.Add(GetSelect(item));
                }
                else if (item.Name == "input")
                {
                    var inputType = item.GetAttributeValue("type", "");
                    // 忽略submit reset button按钮
                    if (ignoreInputType.Contains(inputType))
                    {
                        Console.WriteLine($"忽略input type={inputType}");
                        return;
                    }
                    controls.Add(GetInput(item));
                }
                else if (item.Name == "textarea")
                {
                    controls.Add(GetTextarea(item));
                }
            }
        }

        InputInfo GetInput(HtmlNode item)
        {
            var classString = item.GetAttributeValue("class", "");
            var iType = item.GetAttributeValue("type", "");
            var inputName = item.GetAttributeValue("name", "");
            var id = item.GetAttributeValue("id", "");
            var label = GetLabel(item);
            InputInfo input = new InputInfo()
            {
                Id = id,
                Label = label,
                Class = classString,
                Name = inputName
            };
            if (Enum.TryParse(iType, out InputType type))
            {
                input.InputType = type;
            }
            else
            {
                throw new Exception($"解析inputType失败,{iType}");
            }
            return input;
        }

        TextareaInfo GetTextarea(HtmlNode item)
        {
            var classString = item.GetAttributeValue("class", "");
            var iType = item.GetAttributeValue("type", "");
            var inputName = item.GetAttributeValue("name", "");
            var id = item.GetAttributeValue("id", "");
            var label = GetLabel(item);
            TextareaInfo input = new TextareaInfo()
            {
                Id = id,
                Label = label,
                Class = classString,
                Name = inputName
            };
            return input;
        }

        /// <summary>
        /// 解析select节点
        /// </summary>
        /// <param name="item"></param>
        SelectInfo GetSelect(HtmlNode item)
        {
            var options = item.Descendants().Where(d => d.Name == "option").Select(i => new OptionInfo
            {
                Text = i.InnerText,
                Value = i.GetAttributeValue("value", "")
            }).ToList();

            var label = GetLabel(item);
            var classString = item.GetAttributeValue("class", "");
            var inputName = item.GetAttributeValue("name", "");
            var multi = item.Attributes.Contains("multiple");
            if (multi)
            {
                throw new Exception("目前不支持select的多选,因为dom操作select的多选值有点复杂，目前先不做");
            }
            var id = item.GetAttributeValue("id", "");
            SelectInfo input = new SelectInfo()
            {
                Id = id,
                Label = label,
                Class = classString,
                Name = inputName,
                Options = options,
                IsMultiple = multi
            };
            return input;
        }

        static string GetLabel(HtmlNode item)
        {
            var lableNode = item.ParentNode;
            string lableString = "Lable未找到,请手动输入...";
            int maxDep = 5;
            int dep = 0;
            while (lableNode != null)
            {
                if (dep > maxDep)
                {
                    break;
                }
                if (!string.IsNullOrWhiteSpace(lableNode.InnerText))
                {
                    lableString = lableNode.InnerText;
                    break;
                }
                dep++;
                lableNode = item.ParentNode;
            }
            return lableString.Trim('\r', ' ', '\n');
        }

        static HashSet<string> intersTag = new HashSet<string>
        {
            "input",
            "select",
            "textarea",
        };

        /// <summary>
        /// 需要忽略的input的类型，如submit,reset
        /// </summary>
        static HashSet<string> ignoreInputType = new HashSet<string>
        {
            "button",
            "reset",
            "submit",
            //"password"
        };
    }
}
